package com.hedl.classroom.service.jobhandler;

import com.hedl.classroom.base.utils.Mp4VideoUtil;
import com.hedl.classroom.model.po.MediaProcess;
import com.hedl.classroom.service.MediaFileProcessService;
import com.hedl.classroom.service.MediaFileService;
import com.xxl.job.core.context.XxlJobHelper;
import com.xxl.job.core.handler.annotation.XxlJob;
import io.minio.GetObjectArgs;
import io.minio.MinioClient;
import io.minio.UploadObjectArgs;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.compress.utils.IOUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

/**
 * ProjectName: onlinec-classroom
 * DateTime: 2023/7/18 12:46
 * Author: Big Watermelon
 * Description: 任务处理类
 */
@Slf4j
@Component
public class VideoTask {

    @Resource
    private MediaFileProcessService mediaFileProcessService;

    @Resource
    private MediaFileService mediaFileService;

    @Resource
    private MinioClient minioClient;

    //ffmpeg的路径
    @Value("${videoprocess.ffmpegpath}")
    private String ffmpegPath;

    /**
     *  视频处理任务
     */
    @XxlJob("videoJobHandler")
    public void videoJobHandler() throws Exception {

        // 分片参数
        int shardIndex = XxlJobHelper.getShardIndex();
        int shardTotal = XxlJobHelper.getShardTotal();
        log.info("当前分片：{},总分片：{}",shardIndex,shardTotal);

        //确定cpu核心数
        int processors = Runtime.getRuntime().availableProcessors();
        log.info("cpu核心数：{}",processors);

        //查询待处理的任务
        List<MediaProcess> mediaProcessList = mediaFileProcessService.getMediaProcessList(shardTotal, shardIndex, processors);
        //任务数量
        int size = mediaProcessList.size();
        log.debug("取到视频处理任务数：{}",size);
        if (size <= 0){
            return;
        }
        //创建一个线程池
        ExecutorService executorService = Executors.newFixedThreadPool(size);
        //使用计数器
        CountDownLatch countDownLatch = new CountDownLatch(size);

        mediaProcessList.forEach(mediaProcess -> {
            //将任务加入线程池
            executorService.execute(()->{
                try {
                    //任务id
                    Long id = mediaProcess.getId();
                    //文件id就是md5
                    String fileId = mediaProcess.getFileId();
                    //开启任务
                    boolean b = mediaFileProcessService.startTask(id);
                    if (!b) {
                        log.debug("抢占任务失败，任务id：{}", id);
                        return;
                    }
                    //执行视频转码
                    //下载minio的视频到本地
                    String bucket = mediaProcess.getBucket();
                    String objectName = mediaProcess.getFilePath();
                    File file = downloadFileFromMinIO(bucket, objectName);
                    if (file == null) {
                        log.debug("下载文件失败，任务id：{},Bucket:{},objectName:{}", id, bucket, objectName);
                        mediaFileProcessService.saveProcessFinishStatus(id, "3", fileId, null, "下载视频到本地失败！");
                        return;
                    }
                    //源avi视频的路径
                    String videoPath = file.getAbsolutePath();

                    //转换后mp4文件的名称
                    String mp4Name = fileId + ".mp4";
                    //转换后mp4文件的路径
                    //创建一个临时文件，作为转换后的文件
                    File tempFile = null;
                    try {
                        tempFile = File.createTempFile("minio", ".mp4");
                    } catch (IOException e) {
                        log.debug("创建临时文件异常：{}", e.getMessage());
                        mediaFileProcessService.saveProcessFinishStatus(id, "3", fileId, null, "创建临时文件异常！");
                        return;
                    }
                    String mp4Path = tempFile.getAbsolutePath();
                    //创建工具类对象
                    Mp4VideoUtil videoUtil = new Mp4VideoUtil(ffmpegPath, videoPath, mp4Name, mp4Path);
                    //开始视频转换，成功将返回success,失败返回原因
                    String result = videoUtil.generateMp4();
                    if (!result.equals("success")) {
                        log.debug("视频转换码失败，原因：{},Bucket:{},objectName:{}", result, bucket, objectName);
                        mediaFileProcessService.saveProcessFinishStatus(id, "3", fileId, null, "视频转换码失败！");
                        return;
                    }
                    //上传到minio
                    String uploadFilePath = getFilePathByMd5(fileId, ".mp4");
                    boolean b1 = addMediaFilesToMinIO(mp4Path, "video/mp4", bucket, uploadFilePath);
                    if (!b1) {
                        log.debug("上传mp4视频到minio失败,ID{}", id);
                        mediaFileProcessService.saveProcessFinishStatus(id, "3", fileId, null, "上传mp4视频到minio失败！");
                        return;
                    }
                    //mp4的url
                    String url = getFilePathByMd5(fileId, ".mp4");

                    //更新任务状态为成功
                    mediaFileProcessService.saveProcessFinishStatus(id, "2", fileId, url, result);
                }finally {
                    //计数器减一
                    countDownLatch.countDown();
                }

            });
        });
        //阻塞,指定最大等待时间
        countDownLatch.await(30, TimeUnit.MINUTES);

    }

    /**
     * 根据MD5和文件扩展名，生成文件路径，例 /2/f/2f6451sdg/2f6451sdg.mp4
     * @param fileMd5 文件md5
     * @param fileExt 文件扩展名
     * @return
     */
    private String getFilePathByMd5(String fileMd5,String fileExt){
        return fileMd5.substring(0,1)+"/"+fileMd5.substring(1,2)+"/"+fileMd5+"/"+fileMd5+fileExt;
    }

    /**
     * 从minio下载文件
     * @param bucket 桶
     * @param objectName 对象名称
     * @return 下载后的文件
     */
    private File downloadFileFromMinIO(String bucket,String objectName){
        //临时文件
        File minioFile = null;
        FileOutputStream outputStream = null;
        try{
            InputStream stream = minioClient.getObject(GetObjectArgs.builder()
                    .bucket(bucket)
                    .object(objectName)
                    .build());
            //创建临时文件
            minioFile= File.createTempFile("minio", ".merge");
            outputStream = new FileOutputStream(minioFile);
            IOUtils.copy(stream,outputStream);
            return minioFile;
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            if(outputStream!=null){
                try {
                    outputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return null;
    }

    /**
     * 将文件上传到minio
     * @param localFilePath 文件本地路径
     * @param mimeType 媒体类型
     * @param bucket 桶
     * @param objectName 对象名
     * @return
     */
    public boolean addMediaFilesToMinIO(String localFilePath,String mimeType,String bucket, String objectName){
        try {
            UploadObjectArgs uploadObjectArgs = UploadObjectArgs.builder()
                    .bucket(bucket)//桶
                    .filename(localFilePath) //指定本地文件路径
                    .object(objectName)//对象名 放在子目录下
                    .contentType(mimeType)//设置媒体文件类型
                    .build();
            //上传文件
            minioClient.uploadObject(uploadObjectArgs);
            log.debug("上传文件到minio成功,bucket:{},objectName:{},错误信息:{}",bucket,objectName);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            log.error("上传文件出错,bucket:{},objectName:{},错误信息:{}",bucket,objectName,e.getMessage());
        }
        return false;
    }
}
